Skip to content

fix(chat): render agent event tool results reliably#4524

Merged
senamakel merged 8 commits into
tinyhumansai:mainfrom
senamakel:fix/chat-ui-agent-event-rendering
Jul 5, 2026
Merged

fix(chat): render agent event tool results reliably#4524
senamakel merged 8 commits into
tinyhumansai:mainfrom
senamakel:fix/chat-ui-agent-event-rendering

Conversation

@senamakel

@senamakel senamakel commented Jul 4, 2026

Copy link
Copy Markdown
Member

Summary

  • Forward real tool-result payloads from the web progress bridge into persisted turn state.
  • Render persisted and live tool results in the conversation timeline instead of dropping outputs during thread switches.
  • Preserve live chat runtime state while hydrating selected threads from snapshots.
  • Dedupe run-ledger subagent rows against live timeline rows using stable task IDs.
  • Add focused frontend, Rust mirror/store, and Rust E2E coverage for the event payload and snapshot paths.

Problem

  • Tool results emitted by agent events could lose their real output payload before reaching the web channel and persisted turn-state mirror.
  • Switching threads could hydrate from a stale snapshot and hide live runtime rows that were still active.
  • Subagent progress could appear twice when both run-ledger and live timeline entries described the same task.

Solution

  • Extend the progress bridge and turn-state mirror to carry structured tool result output through to persisted snapshots.
  • Teach chat runtime hydration to preserve active live state and merge snapshot data without clobbering live event rows.
  • Update conversation timeline rendering and ToolTimelineBlock coverage for tool-result display.
  • Use task IDs to filter duplicate subagent ledger rows while retaining distinct work entries.

Submission Checklist

If a section does not apply to this change, mark the item as N/A with a one-line reason. Do not delete items.

  • Tests added or updated (happy path + at least one failure / edge case) per Testing Strategy
  • Diff coverage ≥ 80% — changed lines (Vitest + cargo-llvm-cov merged via diff-cover) meet the gate enforced by .github/workflows/pr-ci.yml. CI will enforce the final changed-line gate for this PR.
  • N/A: Coverage matrix updated — chat-runtime event rendering is covered by focused tests and existing matrix rows are not added/removed/renamed.
  • N/A: No new or renamed feature IDs in docs/TEST-COVERAGE-MATRIX.md.
  • No new external network dependencies introduced (mock backend used per Testing Strategy)
  • N/A: Manual smoke checklist unchanged; this does not touch release-cut surfaces.
  • N/A: No linked issue was provided for this branch.

Impact

  • Desktop app chat timelines now show tool-result output consistently across live events, persisted snapshots, and thread switching.
  • Rust core turn-state persistence stores the additional event payload fields needed by the UI.
  • No new external services, secrets, or migration steps.

Related

  • Closes: N/A
  • Follow-up PR(s)/TODOs: N/A

AI Authored PR Metadata (required for Codex/Linear PRs)

Keep this section for AI-authored PRs. For human-only PRs, mark each field N/A.

Linear Issue

  • Key: N/A
  • URL: N/A

Commit & Branch

  • Branch: fix/chat-ui-agent-event-rendering
  • Commit SHA: 4aeb4c6

Validation Run

  • pnpm --filter openhuman-app format:check
  • pnpm typecheck
  • Focused tests: exact frontend chat tests passed; compact/process panel review regression tests passed; scoped result-output review regression passed; subagent transcript result rehydration regression passed; chatService socket-wrapper test fix passed; CI-shaped vitest changed coverage passed; pnpm typecheck passed; cargo turn_state filter passed; memory_threads_raw_coverage_e2e passed; PR checklist passed; localized README anchor grep and Prettier passed
  • Rust fmt/check (if changed): cargo fmt --manifest-path ../Cargo.toml --all --check via app format hook
  • Tauri fmt/check (if changed): cargo fmt --manifest-path app/src-tauri/Cargo.toml --all --check; pnpm rust:check

Validation Blocked

  • command: N/A
  • error: N/A
  • impact: N/A

Behavior Changes

  • Intended behavior change: chat timelines retain and render agent tool result output, including after snapshot hydration and thread switches.
  • User-visible effect: users can see completed tool outputs and avoid duplicate subagent rows for the same task.

Parity Contract

  • Legacy behavior preserved: existing chat runtime and thread snapshot rendering remain supported; new fields are additive.
  • Guard/fallback/dispatch parity checks: frontend and Rust tests cover missing/legacy payload paths and live-vs-snapshot merge behavior.

Duplicate / Superseded PR Handling

  • Duplicate PR(s): N/A
  • Canonical PR: this PR
  • Resolution (closed/superseded/updated): N/A

Note: initial git push ran the pre-push hook through format, lint, typecheck, rust:check, and command-token lint successfully, then GitHub closed the SSH sideband after the long hook. The retry used --no-verify only to avoid re-running already completed local checks before uploading the same commit.

Summary by CodeRabbit

  • New Features
    • Tool results now appear in the conversation view, including expandable output for completed tools and sub-agent steps.
    • Conversations stay visible while live activity is in progress, avoiding a premature empty/new-window state.
  • Bug Fixes
    • Improved rehydration so active threads keep their live progress, tool results, and approval state instead of being overwritten by stale snapshots.
    • Prevented duplicate tool timeline entries when the same delegation is already shown live.
  • Documentation
    • Fixed updated contribution links in localized README files.

senamakel added 4 commits July 4, 2026 14:36
…persist them in turn-state

- tool_result socket events now carry the size-capped tool output (matching
  the subagent_tool_result path) instead of a {output_chars, elapsed_ms}
  metadata stub — the frontend's ChatToolResultEvent.output and the
  propose_workflow proposal parser already expected the real payload.
- ToolTimelineEntry / SubagentToolCall snapshots persist a 64KiB-capped
  output so the rehydrated 'View processing' timeline can show what each
  tool returned after a thread switch or cold boot.
- The tinyagents event bridge's silent catch-all now trace-logs dropped
  event kinds.

Claude-Session: https://claude.ai/code/session_011S48qZxBBHUbaYMZTngZ5M
…switches, harden chat event subscription

- ToolTimelineEntry gains a 'result' field: set live from the tool_result
  socket event's output and rehydrated from the persisted snapshot's new
  'output' field; ToolTimelineBlock renders it as a scrollable result block.
- hydrateRuntimeFromSnapshot no longer clobbers a thread whose live socket
  driver is mid-turn: the global provider keeps feeding Redux while the user
  is on another tab, so applying a flush-boundary snapshot wiped streamed
  prose, tool results, and pending approval cards. Snapshots now apply only
  when there is no live lifecycle (cold boot / new window / interrupted).
- The chat message pane now renders when live agent activity exists even
  before thread history loads (previously gated solely on hasVisibleMessages,
  blanking tool calls + streaming on thread switch).
- subscribeChatEvents registers through the socketService wrapper so chat
  listeners queue while the socket is connecting instead of silently
  no-opping, and re-attach on reconnect.

Claude-Session: https://claude.ai/code/session_011S48qZxBBHUbaYMZTngZ5M
…live-driver hydration guard

- progress_bridge: tokio test asserting the tool_result wire event carries
  the real output (not the metadata stub).
- turn_state mirror: parent + subagent output persistence, 64KiB cap with
  char-boundary truncation, empty-output elision.
- chatRuntimeSlice: live-driver guard (tab-switch no-clobber, cold-boot
  apply) and persisted output → result mapping.
- ChatRuntimeProvider: tool_result attaches output as the row result;
  empty output leaves it unset. ToolTimelineBlock renders the result block.
- fix stale observability test: vendored tinyagents dropped
  ToolCompleted.started_at_ms (pre-existing lib-test compile breakage).

Claude-Session: https://claude.ai/code/session_011S48qZxBBHUbaYMZTngZ5M
…by taskId

With the live-driver hydration guard, live rows keep their socket-path entry
ids, so the ledger merge (which keys rows as subagent:<runId>) could add a
second row for a delegation already on screen. Dedupe on the subagent taskId.

Claude-Session: https://claude.ai/code/session_011S48qZxBBHUbaYMZTngZ5M
@senamakel senamakel requested a review from a team July 4, 2026 23:38
@coderabbitai

coderabbitai Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR persists and surfaces tool call output end-to-end: Rust turn-state mirror/types now cap and store tool result text, the web progress bridge forwards real output instead of a metadata stub, and frontend runtime/UI map, hydrate, and render that result in the tool timeline. Conversations.tsx gains a live-activity flag to prevent empty-state flicker. Unrelated README anchor fixes are included.

Changes

Tool output persistence and display

Layer / File(s) Summary
Persisted output field and capping helper
src/openhuman/threads/turn_state/types.rs, src/openhuman/threads/turn_state/mirror.rs
Adds output: Option<String> to ToolTimelineEntry/SubagentToolCall and a char-boundary truncation helper capping persisted tool output.
Mirror observe wiring and tests
src/openhuman/threads/turn_state/mirror.rs, mirror_tests.rs, store_tests.rs, tests/memory_*_e2e.rs
Wires output capture/capping into tool/subagent event handlers, initializes placeholders to None, and updates/adds tests for capping and shape consistency.
Web channel output forwarding
src/openhuman/channels/providers/web/progress_bridge.rs
Forwards the tool's real (capped) output in tool_result events instead of a stringified metadata stub; adds a verifying test.
Frontend persisted types and mapping
app/src/types/turnState.ts, app/src/store/chatRuntimeSlice.ts
Adds output? to persisted interfaces, result? to ToolTimelineEntry, and maps persisted output into result during rehydration.
Runtime result updates and hydration guards
app/src/providers/ChatRuntimeProvider.tsx, app/src/store/chatRuntimeSlice.ts, related *.test.ts(x)
Stores non-empty tool output into timeline row result; adds a live-driver guard preventing stale snapshot hydration from clobbering in-flight state and dedupes ledger rows by taskId.
UI result rendering and activity flag
app/src/pages/conversations/components/ToolTimelineBlock.tsx, app/src/pages/Conversations.tsx, tests
Renders entry.result in an expandable <pre> block; introduces hasLiveAgentActivity to keep the message pane populated during live agent activity.
Socket subscription wrapper
app/src/services/chatService.ts
Routes chat event subscription through socketService.on/off instead of grabbing the raw socket directly.
Trace logging for unforwarded events
src/openhuman/tinyagents/observability.rs
Logs unforwarded AgentEvent variants at trace level instead of silently ignoring them.

CONTRIBUTING-BEGINNERS anchor fix

Layer / File(s) Summary
README anchor updates
docs/README.de.md, docs/README.ja-JP.md, docs/README.ko.md, docs/README.ur-pk.md, docs/README.zh-CN.md
Updates the fragment anchor referencing the "let an AI coding agent guide you" section from single-dash to double-dash form.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
    participant Agent as Tool Execution
    participant Bridge as progress_bridge.rs
    participant Mirror as TurnStateMirror
    participant Store as ChatRuntimeProvider/Slice
    participant UI as ToolTimelineBlock

    Agent->>Bridge: ToolCallCompleted(output)
    Bridge->>Bridge: cap_wire_output(output)
    Bridge-->>Store: tool_result event(output)
    Store->>Store: derive result from event.output (if non-empty)
    Store->>Store: update ToolTimelineEntry.result

    Agent->>Mirror: ToolCallCompleted/SubagentToolCallCompleted(output)
    Mirror->>Mirror: cap_persisted_output(output)
    Mirror->>Mirror: persist output on entry/subagent call

    Store->>Store: hydrateRuntimeFromSnapshot
    alt live driver active (started/streaming)
        Store->>Store: skip overwrite, apply taskBoard only
    else no live driver
        Store->>Store: map persisted output -> result, apply snapshot
    end

    Store-->>UI: ToolTimelineEntry with result
    UI->>UI: render resultContent in expandable <pre>
Loading

Possibly related PRs

  • tinyhumansai/openhuman#4413: Both PRs modify ChatRuntimeProvider.tsx's onToolResult handling to enrich ToolTimelineEntry data.
  • tinyhumansai/openhuman#4506: Extends AgentProgress::ToolCallCompleted payloads with real output, consumed by this PR's persistence/rendering flow.

Suggested labels: rust-core, agent, bug

Suggested reviewers: sanil-23, M3gA-Mind

Poem

A tool once spoke but none could hear,
its output lost, then found so clear.
I capped its words, tucked safe in stone,
and rendered them where results are shown.
Hop hop hooray, no more empty pane —
🐇 the timeline glows with output again!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: reliably rendering chat tool results end-to-end.

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 885d885f24

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread app/src/pages/conversations/components/ToolTimelineBlock.tsx
@coderabbitai coderabbitai Bot added agent Built-in agents, prompts, orchestration, and agent runtime in src/openhuman/agent/. bug rust-core Core Rust runtime in src/: CLI, core_server, shared infrastructure. labels Jul 4, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 08f18ccd66

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread app/src/pages/conversations/components/AgentProcessSourcePanel.tsx Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
src/openhuman/channels/providers/web/progress_bridge.rs (1)

468-514: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

MAX_WIRE_SUBAGENT_OUTPUT name no longer matches its usage.

This constant now also caps the main-agent tool_result output (previously only the subagent subagent_tool_result path used it). Consider renaming to something scope-neutral (e.g. MAX_WIRE_TOOL_OUTPUT) to avoid confusing future changes that only intend to affect subagent behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhuman/channels/providers/web/progress_bridge.rs` around lines 468 -
514, The constant name is too subagent-specific even though it is now used in
the main-agent tool result flow in progress_bridge.rs. Rename
MAX_WIRE_SUBAGENT_OUTPUT to a scope-neutral name like MAX_WIRE_TOOL_OUTPUT, and
update its uses in cap_wire_output and the WebChannelEvent
tool_result/subagent_tool_result paths so the name matches the shared behavior.
src/openhuman/threads/turn_state/mirror.rs (1)

27-58: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicated truncation logic vs. progress_bridge.rs::cap_wire_output.

cap_persisted_output here and cap_wire_output in src/openhuman/channels/providers/web/progress_bridge.rs implement the same char-boundary truncation-with-marker algorithm (each with its own TRUNCATION_MARKER_BUDGET constant and cap). Consider extracting a shared helper (e.g. fn truncate_utf8_with_marker(s: &str, max_len: usize, marker_budget: usize) -> Cow<str>) into a common module so the two call sites can't silently diverge (e.g. only one handles the empty-output → None case today).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhuman/threads/turn_state/mirror.rs` around lines 27 - 58,
`cap_persisted_output` in `mirror.rs` duplicates the same UTF-8
truncation-and-marker behavior already implemented by `cap_wire_output` in
`progress_bridge.rs`, so refactor both call sites to use a shared helper in a
common module. Extract the char-boundary truncation logic and marker budgeting
into one reusable function (for example, a helper returning a `Cow<str>`), then
adapt `cap_persisted_output` and `cap_wire_output` to preserve their existing
empty-output behavior while relying on that shared implementation.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/openhuman/channels/providers/web/progress_bridge.rs`:
- Around line 468-514: The constant name is too subagent-specific even though it
is now used in the main-agent tool result flow in progress_bridge.rs. Rename
MAX_WIRE_SUBAGENT_OUTPUT to a scope-neutral name like MAX_WIRE_TOOL_OUTPUT, and
update its uses in cap_wire_output and the WebChannelEvent
tool_result/subagent_tool_result paths so the name matches the shared behavior.

In `@src/openhuman/threads/turn_state/mirror.rs`:
- Around line 27-58: `cap_persisted_output` in `mirror.rs` duplicates the same
UTF-8 truncation-and-marker behavior already implemented by `cap_wire_output` in
`progress_bridge.rs`, so refactor both call sites to use a shared helper in a
common module. Extract the char-boundary truncation logic and marker budgeting
into one reusable function (for example, a helper returning a `Cow<str>`), then
adapt `cap_persisted_output` and `cap_wire_output` to preserve their existing
empty-output behavior while relying on that shared implementation.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c26f03cc-6e10-4839-b825-7d644ac10859

📥 Commits

Reviewing files that changed from the base of the PR and between 42ce5c0 and f8042d7.

📒 Files selected for processing (22)
  • app/src/pages/Conversations.tsx
  • app/src/pages/conversations/components/ToolTimelineBlock.tsx
  • app/src/pages/conversations/components/__tests__/ToolTimelineBlock.test.tsx
  • app/src/providers/ChatRuntimeProvider.tsx
  • app/src/providers/__tests__/ChatRuntimeProvider.test.tsx
  • app/src/services/chatService.ts
  • app/src/store/chatRuntimeSlice.test.ts
  • app/src/store/chatRuntimeSlice.ts
  • app/src/types/turnState.ts
  • docs/README.de.md
  • docs/README.ja-JP.md
  • docs/README.ko.md
  • docs/README.ur-pk.md
  • docs/README.zh-CN.md
  • src/openhuman/channels/providers/web/progress_bridge.rs
  • src/openhuman/threads/turn_state/mirror.rs
  • src/openhuman/threads/turn_state/mirror_tests.rs
  • src/openhuman/threads/turn_state/store_tests.rs
  • src/openhuman/threads/turn_state/types.rs
  • src/openhuman/tinyagents/observability.rs
  • tests/memory_raw_coverage_e2e.rs
  • tests/memory_threads_raw_coverage_e2e.rs

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 5, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3c9ac927a2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread app/src/store/chatRuntimeSlice.ts
@senamakel senamakel merged commit 87f223b into tinyhumansai:main Jul 5, 2026
15 of 19 checks passed
@github-project-automation github-project-automation Bot moved this from Todo to Done in Team Openhuman Jul 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agent Built-in agents, prompts, orchestration, and agent runtime in src/openhuman/agent/. bug rust-core Core Rust runtime in src/: CLI, core_server, shared infrastructure.

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant